package demo.java.util.concurrent;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.atomic.AtomicLong;

public class ConcurrentMapDemo {

    private static Logger logger = LoggerFactory.getLogger(ConcurrentMapDemo.class);

    public ConcurrentMap<String, Object> concurrentMap = null;

    @Test
    public void testPutIfAbsent() {
        ConcurrentHashMap<String, Integer> cache = new ConcurrentHashMap<>();
        for (int a = 0; a < 10; a++) {
            Integer value = cache.putIfAbsent("a", a);
            System.out.println("cache = " + cache + ",value =" + value);
        }
    }

    /**
     * computeIfAbsent方法：
     * <li>在指定的键不存在时，会根据提供的函数计算出一个默认值，并将其存储到 Map 中，最后返回该默认值。
     * <li>如果键已经存在于 Map 中，则直接返回该键对应的值，而不会调用提供的函数。
     */
    @Test
    public void testComputeIfAbsent() {
        ConcurrentHashMap<String, List<String>> map = new ConcurrentHashMap<>();

        map.computeIfAbsent("zhangsan", k -> new ArrayList<>())
                .add("apple");
        System.out.println(map);
        map.computeIfAbsent("zhangsan", k -> new ArrayList<>())
                .add("orange");
        System.out.println(map);
        map.computeIfAbsent("lisi", k -> new ArrayList<>())
                .add("water");
        System.out.println(map);
    }

    @Test
    public void toStr() {
        ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong>();
        map.put("aaa", new AtomicLong(50));
        logger.info("map:{}", map);
    }

    @Test
    public void testEnumeration() {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
        Arrays.asList(1, 2, 3, 4, 5).forEach(e -> map.put(e.toString(), e));
        System.out.println(map);
        Enumeration<String> keys = map.keys();
        while (keys.hasMoreElements()) {
            String appName = keys.nextElement();
            Integer value = map.remove(appName);
            System.err.println(value);
        }
        System.out.println(map);
    }

    /**
     * 最重要的实现ConcurrentHashMap使用了一些新的方法来改进，便于在映射上执行并行操作。就像并行流那样，这些方法使用特定的ForkJoinPool，由Java8中的ForkJoinPool.commonPool()提供。
     * Java8引入了三种类型的并行操作：forEach、search 和
     * reduce。这些操作中每个都以四种形式提供，接受以键、值、元素或键值对为参数的函数。
     * 所有这些方法的第一个参数是通用的parallelismThreshold。这一阈值表示操作并行执行时的最小集合大小。例如，如果你传入阈值500，而映射的实际大小是499，那么操作就会在单线程上串行执行。
     * 在下一个例子中，我们使用阈值1，始终强制并行执行来展示。
     *
     * <h2>ConcurrentHashMap 和 Hashtable 的区别？</h2>
     * <p>
     * ConcurrentHashMap 结合了 HashMap 和 HashTable 二者的优势。 HashMap 没有考虑同步，hashtable
     * 考虑了同步的问题。但是 hashtable 在每次同步执行时都要锁住整个结构。 ConcurrentHashMap 锁的方式是稍微细粒度的。
     * ConcurrentHashMap 将 hash 表分为 16 个桶（默认值），诸如 get,put,remove 等常用操作只锁当前需要用到的桶。
     * <p>
     * <li>该类包含两个静态内部类 HashEntry 和 Segment；前者用来封装映射表的键值对，后者用来充当锁的角色；
     * <li>Segment 是一种可重入的锁 ReentrantLock，每个 Segment 守护一个 HashEntry 数组里得元素，当对
     * HashEntry 数组的数据进行修改时，必须首先获得对应的 Segment 锁。
     * <p>
     */
    @Test
    public void concurrentHashMap() {
        // 这个值可以通过设置下列JVM参数来增减：-Djava.util.concurrent.ForkJoinPool.common.parallelism=5
        System.out.println("ForkJoinPool.getCommonPoolParallelism() = " + ForkJoinPool.getCommonPoolParallelism());
        ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
        map.put("foo", "bar");
        map.put("han", "solo");
        map.put("r2", "d2");
        map.put("c3", "p0");
        System.out.println("------------forEach-------------");
        // forEach()方法可以并行迭代映射中的键值对
        map.forEach(1, (key, value) -> logger.info("key: {}; value: {};", key, value));
        System.out.println("------------search-------------");
        /*
         * search()方法接受BiFunction并为当前的键值对返回一个非空的搜索结果，或者在当前迭代不匹配任何搜索条件时返回null。只要返回了非空的结果，
         * 就不会往下搜索了。
         * 要记住ConcurrentHashMap是无序的。搜索函数应该不依赖于映射实际的处理顺序。如果映射的多个元素都满足指定搜索函数，结果是非确定的。
         */
        String result = map.search(1, (key, value) -> {
            logger.info(Thread.currentThread().getName());
            if ("foo".equals(key)) {
                return value;
            }
            return null;
        });
        logger.info("Result: " + result);
        System.out.println("------------searchValues-------------");
        String searchValues = map.searchValues(1, value -> {
            logger.info(Thread.currentThread().getName());
            if (value.length() > 3) {
                return value;
            }
            return null;
        });

        logger.info("Result: " + searchValues);
        System.out.println("------------reduce-------------");
        String reduceResult = map.reduce(1, (key, value) -> {
            logger.info("Transform: " + Thread.currentThread().getName());
            return key + "=" + value;
        }, (s1, s2) -> {
            logger.info("Reduce: " + Thread.currentThread().getName());
            return s1 + ", " + s2;
        });

        logger.info("Result: " + reduceResult);
        map.values();
    }

    /**
     * ConcurrentMap 的key 和 value 都不能为null，否则抛出异常，如下：
     *
     * <pre>
     * if (key == null || value == null)
     *     throw new NullPointerException();
     * </pre>
     *
     * <h2>ConcurrentHashMap 和 Hashtable 的区别？</h2>
     * <p>
     * ConcurrentHashMap 结合了 HashMap 和 HashTable 二者的优势。 HashMap 没有考虑同步，hashtable
     * 考虑了同步的问题。但是 hashtable 在每次同步执行时都要锁住整个结构。 ConcurrentHashMap 锁的方式是稍微细粒度的。
     * ConcurrentHashMap 将 hash 表分为 16 个桶（默认值），诸如 get,put,remove 等常用操作只锁当前需要用到的桶。
     * <p>
     * <li>该类包含两个静态内部类 HashEntry 和 Segment；前者用来封装映射表的键值对，后者用来充当锁的角色；
     * <li>Segment 是一种可重入的锁 ReentrantLock，每个 Segment 守护一个 HashEntry 数组里得元素，当对
     * HashEntry 数组的数据进行修改时，必须首先获得对应的 Segment 锁。
     */
    @Test
    public void concurrentMap() {
        ConcurrentMap<Object, Object> map = new ConcurrentHashMap<>();
        Object o1 = map.put(1, "a");
        System.out.println(o1);
        // putIfAbsent()只在提供的键不存在时，将新的值添加到映射中。
        Object o2 = map.putIfAbsent(2, "b");
        System.out.println(o2);
        Object o3 = map.putIfAbsent(2, "c");
        System.out.println(o3);
        map.forEach((key, value) -> System.out.printf("%s = %s\n", key, value));
        // getOrDefault()方法返回指定键的值。在传入的键不存在时，会返回默认值
        Object value = map.getOrDefault("hi", "there");
        System.out.println(value); // there
        // 替换映射中的所有值
        map.replaceAll((k, v) -> ((String) v).toUpperCase());
        map.forEach((key, v) -> System.out.printf("%s = %s\n", key, v));
        // compute()允许我们转换单个元素，而不是替换映射中的所有值。
        map.compute(2, (k, v) -> k.toString() + v.toString());
        map.forEach((key, v) -> System.out.printf("%s = %s\n", key, v));
        // merge()方法可以用于以映射中的现有值来统一新的值。这个方法接受键、需要并入现有元素的新值，以及指定两个值的合并行为的BiFunction。
        map.merge(2, "boo", (oldVal, newVal) -> newVal + " was " + oldVal);
        map.forEach((key, v) -> System.out.printf("key: %s; value: %s; thread: %s\n", key, value,
                Thread.currentThread().getName()));

    }

}
